package com.google.code.mergeinformer.service.impl;

import com.google.code.mergeinformer.config.SvnClientManagerFactory;
import com.google.code.mergeinformer.model.*;
import com.google.code.mergeinformer.service.VcsService;
import com.google.code.mergeinformer.util.AppUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.wc.SVNRevision;

import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

/**
 * @author Dmitry Golubev
 */
@Service
public class SvnService implements VcsService {

    private static final Pattern messageBasedExclusionPattern;
    private static final List<String> excludedAuthors;

    static {
        final String pattern = AppUtils.getProperty("app.svn.revision.exclude.messageBasedPattern");
        messageBasedExclusionPattern = StringUtils.isNotBlank(pattern) ? Pattern.compile(pattern) : null;

        excludedAuthors = new LinkedList<>();
        final String authors = AppUtils.getProperty("app.svn.revision.exclude.authors");
        if (authors.contains(",")) {
            final StringTokenizer tokenizer = new StringTokenizer(authors, ",");
            while (tokenizer.hasMoreTokens()) {
                final String author = tokenizer.nextToken().trim();
                if (StringUtils.isNotBlank(author)) {
                    excludedAuthors.add(author);
                }
            }
        } else if (StringUtils.isNotBlank(authors)) {
            excludedAuthors.add(authors.trim());
        }
    }

    @Autowired
    private SvnClientManagerFactory svnClientManagerFactory;

    @Override
    public List<Branch> discoverBranches(Project project) throws Exception {
        final List<Branch> branches = new LinkedList<>();
        final String trunkUrl = project.getVcsAttributes().getUrl().replaceAll("/$", "") + "/trunk";
        if (checkUrl(trunkUrl, project.getVcsAttributes()) == VcsAttributesCheckResult.OK) {
            branches.add(new Branch("trunk", trunkUrl));
        }
        final ISVNDirEntryHandler handler = new ISVNDirEntryHandler() {
            @Override
            public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException {
                if (StringUtils.isNotBlank(dirEntry.getName())) {
                    branches.add(new Branch(dirEntry.getName(), dirEntry.getURL().toDecodedString()));
                }
            }
        };
        final VcsAttributes vcsAttributes = project.getVcsAttributes();
        try {
            svnClientManagerFactory.getInstance(vcsAttributes.getUsername(), vcsAttributes.getPassword()).getLogClient().doList(
                    SVNURL.parseURIEncoded(project.getVcsAttributes().getUrl().replaceAll("/$", "") + "/branches"),
                    SVNRevision.HEAD,
                    SVNRevision.HEAD,
                    false,
                    false,
                    handler
            );
        } catch (Exception ignored) {
        }
        return branches;
    }

    @Override
    public List<Revision> getUnmergedRevisions(Task task) throws Exception {
        final List<Revision> revisions = new LinkedList<>();
        final ISVNLogEntryHandler handler = new ISVNLogEntryHandler() {
            @Override
            public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
                if (!excludedAuthors.contains(logEntry.getAuthor())
                        && (messageBasedExclusionPattern == null || !messageBasedExclusionPattern.matcher(logEntry.getMessage()).matches())) {
                    revisions.add(new Revision(
                            logEntry.getRevision(),
                            logEntry.getDate(),
                            logEntry.getAuthor(),
                            logEntry.getMessage()));
                }
            }
        };
        final VcsAttributes vcsAttributes = task.getProject().getVcsAttributes();
        svnClientManagerFactory.getInstance(vcsAttributes.getUsername(), vcsAttributes.getPassword()).getDiffClient().doGetLogEligibleMergeInfo(
                SVNURL.parseURIEncoded(task.getUrlTo()),
                SVNRevision.HEAD,
                SVNURL.parseURIEncoded(task.getUrlFrom()),
                SVNRevision.HEAD,
                false,
                null,
                handler
        );
        return revisions;
    }

    @Override
    public boolean isUrlValid(String url) {
        try {
            SVNURL.parseURIEncoded(url);
            return true;
        } catch (SVNException e) {
            return false;
        }
    }

    @Override
    public VcsAttributesCheckResult checkAttributes(VcsAttributes vcsAttributes) {
        return checkUrl(vcsAttributes.getUrl(), vcsAttributes);
    }

    private VcsAttributesCheckResult checkUrl(String url, VcsAttributes vcsAttributes) {
        final SVNURL svnUrl;
        try {
            svnUrl = SVNURL.parseURIEncoded(url);
        } catch (SVNException e) {
            return VcsAttributesCheckResult.INVALID_URL_FORMAT;
        }

        try {
            svnClientManagerFactory.getInstance(vcsAttributes.getUsername(), vcsAttributes.getPassword()).getLogClient().doList(
                    svnUrl,
                    SVNRevision.HEAD,
                    SVNRevision.HEAD,
                    false,
                    SVNDepth.EMPTY,
                    SVNDirEntry.DIRENT_KIND,
                    new ISVNDirEntryHandler() {
                        @Override
                        public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException {
                            // no-op handler
                        }
                    }
            );
        } catch (SVNException e) {
            if (e.getErrorMessage().getErrorCode().isAuthentication()) {
                return VcsAttributesCheckResult.AUTHENTICATION_FAILURE;
            } else if (e.isEnoent()) {
                return VcsAttributesCheckResult.NON_EXISTENT_ENTRY;
            } else {
                return VcsAttributesCheckResult.UNREACHABLE_URL;
            }
        }
        return VcsAttributesCheckResult.OK;
    }
}
